home *** CD-ROM | disk | FTP | other *** search
/ Enter 2006 September / Enter 09 2006.iso / Internet / SpamExperts Home 1.1 / SpamExperts Home.exe / lib / spamexperts.modules / spambayes / ProxyUI.pyc (.txt) < prev    next >
Encoding:
Python Compiled Bytecode  |  2006-07-14  |  23.1 KB  |  708 lines

  1. # Source Generated with Decompyle++
  2. # File: in.pyc (Python 2.4)
  3.  
  4. '''POP3Proxy and SMTPProxy Web Interface
  5.  
  6. Classes:
  7.     ProxyUserInterface - Interface class for pop3proxy and smtpproxy
  8.  
  9. Abstract:
  10.  
  11. This module implements a browser based Spambayes user interface for the
  12. POP3, IMAP4 SMTP proxies.  Users may use it to interface with the
  13. proxies.
  14.  
  15. The following functions are currently included:
  16. [From the base class UserInterface]
  17.   onClassify - classify a given message
  18.   onWordquery - query a word from the database
  19.   onTrain - train a message or mbox
  20.   onSave - save the database and possibly shutdown
  21. [Here]
  22.   onHome - a home page with various options
  23.   onUpload - upload a message for later training (used by proxytee.py)
  24.   onReview - show messages in corpii
  25.   onView - view a message from one of the corpii
  26.   onShowclues - show clues for a message
  27.  
  28. To do:
  29.  
  30. Web training interface:
  31.  
  32.  o Review already-trained messages, and purge them.
  33.  o Add a Today button on the Review page.
  34.  
  35. User interface improvements:
  36.  
  37.  o Can it cleanly dynamically update its status display while having a POP3
  38.    conversation?  Hammering reload sucks.
  39.  
  40.  o Suggestions?
  41. '''
  42. __author__ = 'Richie Hindle <richie@entrian.com>'
  43. __credits__ = 'Tim Peters, Neale Pickett, Tim Stone, all the Spambayes folk.'
  44.  
  45. try:
  46.     (True, False)
  47. except NameError:
  48.     (True, False) = (1, 0)
  49.  
  50. import re
  51. import cgi
  52. import time
  53. import types
  54. import bisect
  55.  
  56. try:
  57.     Set = set
  58. except NameError:
  59.     
  60.     try:
  61.         from sets import Set
  62.     except ImportError:
  63.         from spambayes.compatsets import Set
  64.  
  65.  
  66. import tokenizer
  67. import UserInterface
  68. options = options
  69. from spambayes.Options import _
  70. from email.Iterators import typed_subpart_iterator
  71. parm_ini_map = (('POP3 Proxy Options', None), ('pop3proxy', 'remote_servers'), ('pop3proxy', 'listen_ports'), ('SMTP Proxy Options', None), ('smtpproxy', 'remote_servers'), ('smtpproxy', 'listen_ports'), ('smtpproxy', 'ham_address'), ('smtpproxy', 'spam_address'), ('smtpproxy', 'use_cached_message'), ('Header Options', None), ('Headers', 'notate_to'), ('Headers', 'notate_subject'), ('Storage Options', None), ('Storage', 'persistent_storage_file'), ('Storage', 'messageinfo_storage_file'), ('Storage', 'cache_messages'), ('Storage', 'no_cache_bulk_ham'), ('Storage', 'no_cache_large_messages'), ('Statistics Options', None), ('Categorization', 'ham_cutoff'), ('Categorization', 'spam_cutoff'))
  72. adv_map = ((_('Statistics Options'), None), ('Classifier', 'max_discriminators'), ('Classifier', 'minimum_prob_strength'), ('Classifier', 'unknown_word_prob'), ('Classifier', 'unknown_word_strength'), ('Classifier', 'use_bigrams'), (_('Header Options'), None), ('Headers', 'include_score'), ('Headers', 'header_score_digits'), ('Headers', 'header_score_logarithm'), ('Headers', 'include_thermostat'), ('Headers', 'include_evidence'), ('Headers', 'clue_mailheader_cutoff'), (_('Storage Options'), None), ('Storage', 'persistent_use_database'), ('Storage', 'cache_expiry_days'), ('Storage', 'cache_use_gzip'), ('Storage', 'ham_cache'), ('Storage', 'spam_cache'), ('Storage', 'unknown_cache'), (_('Tokenising Options'), None), ('Tokenizer', 'mine_received_headers'), ('Tokenizer', 'replace_nonascii_chars'), ('Tokenizer', 'summarize_email_prefixes'), ('Tokenizer', 'summarize_email_suffixes'), (_('Training Options'), None), ('Hammie', 'train_on_filter'), (_('Interface Options'), None), ('html_ui', 'display_headers'), ('html_ui', 'display_received_time'), ('html_ui', 'display_score'), ('html_ui', 'display_adv_find'), ('html_ui', 'default_ham_action'), ('html_ui', 'default_spam_action'), ('html_ui', 'default_unsure_action'), ('html_ui', 'ham_discard_level'), ('html_ui', 'spam_discard_level'), ('html_ui', 'allow_remote_connections'), ('html_ui', 'http_authentication'), ('html_ui', 'http_user_name'), ('html_ui', 'http_password'), ('pop3proxy', 'allow_remote_connections'), ('smtpproxy', 'allow_remote_connections'), ('globals', 'language'), (_('POP3 Proxy Options'), None), ('pop3proxy', 'retrieval_timeout'))
  73.  
  74. class ProxyUserInterface(UserInterface.UserInterface):
  75.     '''Serves the HTML user interface for the proxies.'''
  76.     
  77.     def __init__(self, proxy_state, state_recreator):
  78.         global state
  79.         UserInterface.UserInterface.__init__(self, proxy_state.bayes, parm_ini_map, adv_map, proxy_state.lang_manager, proxy_state.stats)
  80.         state = proxy_state
  81.         self.state_recreator = state_recreator
  82.         self.app_for_version = 'SpamBayes Proxy'
  83.         self.previous_sort = None
  84.         if not proxy_state.can_stop:
  85.             self.html._readonly = False
  86.             self.html.shutdownTableCell = ' '
  87.             self.html._readonly = True
  88.         
  89.  
  90.     
  91.     def onHome(self):
  92.         '''Serve up the homepage.'''
  93.         state.buildStatusStrings()
  94.         stateDict = state.__dict__.copy()
  95.         stateDict.update(state.bayes.__dict__)
  96.         statusTable = self.html.statusTable.clone()
  97.         if not state.servers:
  98.             statusTable.proxyDetails = _('No POP3 proxies running.<br/>')
  99.         
  100.         findBox = self._buildBox(_('Word query'), 'query.gif', self.html.wordQuery)
  101.         if not options[('html_ui', 'display_adv_find')]:
  102.             del findBox.advanced
  103.         
  104.         content = self._buildBox(_('Status and Configuration'), 'status.gif', statusTable % stateDict) + self._buildBox(_('Train on proxied messages'), 'train.gif', self.html.reviewText) + self._buildTrainBox() + self._buildClassifyBox() + findBox + self._buildBox(_('Find message'), 'query.gif', self.html.findMessage)
  105.         self._writePreamble(_('Home'))
  106.         self.write(content)
  107.         self._writePostamble(help_topic = 'home_proxy')
  108.  
  109.     
  110.     def onUpload(self, file):
  111.         """Save a message for later training - used by Skip's proxytee.py."""
  112.         file = file.replace('\r\n', '\n').replace('\r', '\n')
  113.         messages = self._convertUploadToMessageList(file)
  114.         for m in messages:
  115.             messageName = state.getNewMessageName()
  116.             message = state.unknownCorpus.makeMessage(messageName, m)
  117.             state.unknownCorpus.addMessage(message)
  118.         
  119.         self.write(_("<p>OK. Return <a href='home'>Home</a>.</p>"))
  120.  
  121.     
  122.     def _keyToTimestamp(self, key):
  123.         '''Given a message key (as seen in a Corpus), returns the timestamp
  124.         for that message.  This is the time that the message was received,
  125.         not the Date header.'''
  126.         return long(key[:10])
  127.  
  128.     
  129.     def _getTimeRange(self, timestamp):
  130.         '''Given a unix timestamp, returns a 3-tuple: the start timestamp
  131.         of the given day, the end timestamp of the given day, and the
  132.         formatted date of the given day.'''
  133.         this = time.localtime(timestamp)
  134.         start = (this[0], this[1], this[2], 0, 0, 0, this[6], this[7], this[8])
  135.         end = time.localtime(time.mktime(start) + 36 * 60 * 60)
  136.         end = (end[0], end[1], end[2], 0, 0, 0, end[6], end[7], end[8])
  137.         date = time.strftime('%A, %B %d, %Y', start)
  138.         return (time.mktime(start), time.mktime(end), date)
  139.  
  140.     
  141.     def _buildReviewKeys(self, timestamp):
  142.         '''Builds an ordered list of untrained message keys, ready for output
  143.         in the Review list.  Returns a 5-tuple: the keys, the formatted date
  144.         for the list (eg. "Friday, November 15, 2002"), the start of the prior
  145.         page or zero if there isn\'t one, likewise the start of the given page,
  146.         and likewise the start of the next page.'''
  147.         allKeys = state.unknownCorpus.keys()
  148.         allKeys.sort()
  149.         if not timestamp:
  150.             if allKeys:
  151.                 timestamp = self._keyToTimestamp(allKeys[-1])
  152.             else:
  153.                 timestamp = time.time()
  154.         
  155.         (start, end, date) = self._getTimeRange(timestamp)
  156.         startKeyIndex = bisect.bisect(allKeys, '%d' % long(start))
  157.         endKeyIndex = bisect.bisect(allKeys, '%d' % long(end))
  158.         keys = allKeys[startKeyIndex:endKeyIndex]
  159.         keys.reverse()
  160.         prior = end = 0
  161.         if startKeyIndex != 0:
  162.             prior = self._keyToTimestamp(allKeys[startKeyIndex - 1])
  163.         
  164.         if endKeyIndex != len(allKeys):
  165.             end = self._keyToTimestamp(allKeys[endKeyIndex])
  166.         
  167.         return (keys, date, prior, start, end)
  168.  
  169.     
  170.     def _sortMessages(self, messages, sort_order, reverse = False):
  171.         '''Sorts the message by the appropriate attribute.  If this was the
  172.         previous sort order, then reverse it.'''
  173.         if sort_order is None or sort_order == 'received':
  174.             messages.sort()
  175.             if self.previous_sort == sort_order:
  176.                 messages.reverse()
  177.                 self.previous_sort = None
  178.             else:
  179.                 self.previous_sort = 'received'
  180.             return messages
  181.         
  182.         tmplist = [ (getattr(x[1], sort_order), x) for x in messages ]
  183.         tmplist.sort()
  184.         return [ x for key, x in tmplist ]
  185.  
  186.     
  187.     def _appendMessages(self, table, keyedMessageInfo, label, sort_order, reverse = False):
  188.         """Appends the rows of a table of messages to 'table'."""
  189.         stripe = 0
  190.         keyedMessageInfo = self._sortMessages(keyedMessageInfo, sort_order, reverse)
  191.         nrows = options[('html_ui', 'rows_per_section')]
  192.         for key, messageInfo in keyedMessageInfo[:nrows]:
  193.             (unused, unused, messageInfo.received) = self._getTimeRange(self._keyToTimestamp(key))
  194.             row = self.html.reviewRow.clone()
  195.             
  196.             try:
  197.                 score = messageInfo.score
  198.             except ValueError:
  199.                 score = None
  200.  
  201.             if label == _('Spam'):
  202.                 if score is not None and score > options[('html_ui', 'spam_discard_level')]:
  203.                     r_att = getattr(row, 'discard')
  204.                 else:
  205.                     r_att = getattr(row, options[('html_ui', 'default_spam_action')])
  206.             elif label == _('Ham'):
  207.                 if score is not None and score < options[('html_ui', 'ham_discard_level')]:
  208.                     r_att = getattr(row, 'discard')
  209.                 else:
  210.                     r_att = getattr(row, options[('html_ui', 'default_ham_action')])
  211.             else:
  212.                 r_att = getattr(row, options[('html_ui', 'default_unsure_action')])
  213.             setattr(r_att, 'checked', 1)
  214.             row.optionalHeadersValues = ''
  215.             for header in options[('html_ui', 'display_headers')]:
  216.                 header = header.lower()
  217.                 text = getattr(messageInfo, '%sHeader' % (header,))
  218.                 if header == 'subject':
  219.                     h = self.html.reviewRow.linkedHeaderValue.clone()
  220.                     h.text.title = messageInfo.bodySummary
  221.                     h.text.href = 'view?key=%s&corpus=%s' % (key, label)
  222.                 else:
  223.                     h = self.html.reviewRow.headerValue.clone()
  224.                 h.text = text
  225.                 row.optionalHeadersValues += h
  226.             
  227.             if options[('html_ui', 'display_score')]:
  228.                 if isinstance(messageInfo.score, types.StringTypes):
  229.                     row.score_ = messageInfo.score
  230.                 else:
  231.                     row.score_ = '%.2f%%' % (messageInfo.score,)
  232.             else:
  233.                 del row.score_
  234.             if options[('html_ui', 'display_received_time')]:
  235.                 row.received_ = messageInfo.received
  236.             else:
  237.                 del row.received_
  238.             subj_list = []
  239.             for c in messageInfo.subjectHeader:
  240.                 subj_list.append('%%%s' % (hex(ord(c))[2:],))
  241.             
  242.             subj = ''.join(subj_list)
  243.             row.classify.href = 'showclues?key=%s&subject=%s' % (key, subj)
  244.             row.tokens.href = 'showclues?key=%s&subject=%s&tokens=1' % (key, subj)
  245.             setattr(row, 'class', [
  246.                 'stripe_on',
  247.                 'stripe_off'][stripe])
  248.             setattr(row, 'onMouseOut', [
  249.                 "this.className='stripe_on';",
  250.                 "this.className='stripe_off';"][stripe])
  251.             row = str(row).replace('TYPE', label).replace('KEY', key)
  252.             table += row
  253.             stripe = stripe ^ 1
  254.         
  255.  
  256.     
  257.     def onReview(self, **params):
  258.         '''Present a list of message for (re)training.'''
  259.         self._writePreamble('Review')
  260.         id = ''
  261.         numTrained = 0
  262.         numDeferred = 0
  263.         if params.get('go') != _('Refresh'):
  264.             for key, value in params.items():
  265.                 if key.startswith('classify:'):
  266.                     (old_class, id) = key.split(':')[1:3]
  267.                     if value == _('spam'):
  268.                         targetCorpus = state.spamCorpus
  269.                         stats_as_ham = False
  270.                     elif value == _('ham'):
  271.                         targetCorpus = state.hamCorpus
  272.                         stats_as_ham = True
  273.                     elif value == _('discard'):
  274.                         targetCorpus = None
  275.                         
  276.                         try:
  277.                             state.unknownCorpus.removeMessage(state.unknownCorpus[id])
  278.                         except KeyError:
  279.                             pass
  280.                         except:
  281.                             None<EXCEPTION MATCH>KeyError
  282.                         
  283.  
  284.                     None<EXCEPTION MATCH>KeyError
  285.                     targetCorpus = None
  286.                     numDeferred += 1
  287.                     if targetCorpus:
  288.                         sourceCorpus = None
  289.                         if state.unknownCorpus.get(id) is not None:
  290.                             sourceCorpus = state.unknownCorpus
  291.                         elif state.hamCorpus.get(id) is not None:
  292.                             sourceCorpus = state.hamCorpus
  293.                         elif state.spamCorpus.get(id) is not None:
  294.                             sourceCorpus = state.spamCorpus
  295.                         
  296.                         if sourceCorpus is not None:
  297.                             
  298.                             try:
  299.                                 targetCorpus.takeMessage(id, sourceCorpus, fromCache = True)
  300.                                 if numTrained == 0:
  301.                                     self.write(_('<p><b>Training... '))
  302.                                     self.flush()
  303.                                 
  304.                                 numTrained += 1
  305.                                 self.stats.RecordTraining(stats_as_ham, old_class = old_class)
  306.                             except KeyError:
  307.                                 pass
  308.                             except:
  309.                                 None<EXCEPTION MATCH>KeyError
  310.                             
  311.  
  312.                         None<EXCEPTION MATCH>KeyError
  313.                     
  314.                 targetCorpus
  315.             
  316.         
  317.         if numTrained > 0:
  318.             plural = ''
  319.             if numTrained == 1:
  320.                 response = 'Trained on one message. '
  321.             else:
  322.                 response = 'Trained on %d messages. ' % (numTrained,)
  323.             self._doSave()
  324.             self.write(response)
  325.             self.write('<br> ')
  326.         
  327.         title = ''
  328.         keys = []
  329.         sourceCorpus = state.unknownCorpus
  330.         if numDeferred > 0:
  331.             start = self._keyToTimestamp(id)
  332.         elif id:
  333.             start = self._keyToTimestamp(id)
  334.             (unused, unused, prior, unused, next) = self._buildReviewKeys(start)
  335.             if prior:
  336.                 start = prior
  337.             else:
  338.                 start = next
  339.         elif params.get('go') == _('Next day'):
  340.             start = self._keyToTimestamp(params['next'])
  341.         elif params.get('go') == _('Previous day'):
  342.             start = self._keyToTimestamp(params['prior'])
  343.         elif params.get('find') is not None:
  344.             prior = next = 0
  345.             keys = Set()
  346.             push = keys.add
  347.             
  348.             try:
  349.                 max_results = int(params['max_results'])
  350.             except ValueError:
  351.                 max_results = 1
  352.  
  353.             key = params['find']
  354.             if params.has_key('ignore_case'):
  355.                 ic = True
  356.             else:
  357.                 ic = False
  358.             error = False
  359.             if key == '':
  360.                 error = True
  361.                 page = _('<p>You must enter a search string.</p>')
  362.             elif len(keys) < max_results and params.has_key('id'):
  363.                 if state.unknownCorpus.get(key):
  364.                     push((key, state.unknownCorpus))
  365.                 elif state.hamCorpus.get(key):
  366.                     push((key, state.hamCorpus))
  367.                 elif state.spamCorpus.get(key):
  368.                     push((key, state.spamCorpus))
  369.                 
  370.             
  371.             if params.has_key('subject') and params.has_key('body') or params.has_key('headers'):
  372.                 self.write(_('<p>Searching...</p>'))
  373.                 for corp in [
  374.                     state.unknownCorpus,
  375.                     state.hamCorpus,
  376.                     state.spamCorpus]:
  377.                     for k in corp.keys():
  378.                         if len(keys) >= max_results:
  379.                             break
  380.                         
  381.                         msg = corp[k]
  382.                         msg.load()
  383.                         if params.has_key('subject'):
  384.                             subj = str(msg['Subject'])
  385.                             if self._contains(subj, key, ic):
  386.                                 push((k, corp))
  387.                             
  388.                         
  389.                         if params.has_key('body'):
  390.                             msg_body = msg.as_string()
  391.                             msg_body = msg_body[msg_body.index('\r\n\r\n'):]
  392.                             if self._contains(msg_body, key, ic):
  393.                                 push((k, corp))
  394.                             
  395.                         
  396.                         if params.has_key('headers'):
  397.                             for nm, val in msg.items():
  398.                                 nm = str(nm)
  399.                                 val = str(val)
  400.                                 if self._contains(nm, key, ic) or self._contains(val, key, ic):
  401.                                     push((k, corp))
  402.                                     continue
  403.                             
  404.                     
  405.                 
  406.             
  407.             if len(keys):
  408.                 if len(keys) == 1:
  409.                     title = _('Found message')
  410.                 else:
  411.                     title = _('Found messages')
  412.                 keys = list(keys)
  413.             else:
  414.                 page = _('<p>Could not find any matching messages. Maybe they expired?</p>')
  415.                 title = _('Did not find message')
  416.                 box = self._buildBox(title, 'status.gif', page)
  417.                 self.write(box)
  418.                 self.write(self._buildBox(_('Find message'), 'query.gif', self.html.findMessage))
  419.                 self._writePostamble()
  420.                 return None
  421.         else:
  422.             start = 0
  423.         if len(keys) == 0:
  424.             (keys, date, prior, this, next) = self._buildReviewKeys(start)
  425.         
  426.         keyedMessageInfo = {
  427.             options[('Headers', 'header_unsure_string')]: [],
  428.             options[('Headers', 'header_ham_string')]: [],
  429.             options[('Headers', 'header_spam_string')]: [] }
  430.         invalid_keys = []
  431.         for key in keys:
  432.             if isinstance(key, types.TupleType):
  433.                 (key, sourceCorpus) = key
  434.             else:
  435.                 sourceCorpus = state.unknownCorpus
  436.             message = sourceCorpus[key]
  437.             
  438.             try:
  439.                 message.load()
  440.             except IOError:
  441.                 invalid_keys.append(key)
  442.                 continue
  443.  
  444.             judgement = message[options[('Headers', 'classification_header_name')]]
  445.             if judgement is None:
  446.                 judgement = options[('Headers', 'header_unsure_string')]
  447.             else:
  448.                 judgement = judgement.split(';')[0].strip()
  449.             messageInfo = self._makeMessageInfo(message)
  450.             keyedMessageInfo[judgement].append((key, messageInfo))
  451.         
  452.         for key in invalid_keys:
  453.             keys.remove(key)
  454.         
  455.         if keys:
  456.             page = self.html.reviewtable.clone()
  457.             if prior:
  458.                 page.prior.value = prior
  459.                 del page.priorButton.disabled
  460.             
  461.             if next:
  462.                 page.next.value = next
  463.                 del page.nextButton.disabled
  464.             
  465.             templateRow = page.reviewRow.clone()
  466.             sort_order = params.get('sort')
  467.             if self.previous_sort == sort_order:
  468.                 reverse = True
  469.                 self.previous_sort = None
  470.             else:
  471.                 reverse = False
  472.                 self.previous_sort = sort_order
  473.             page.table = ''
  474.             for header, label in ((options[('Headers', 'header_unsure_string')], 'Unsure'), (options[('Headers', 'header_ham_string')], 'Ham'), (options[('Headers', 'header_spam_string')], 'Spam')):
  475.                 messages = keyedMessageInfo[header]
  476.                 if messages:
  477.                     sh = self.html.reviewSubHeader.clone()
  478.                     sh.optionalHeaders = ''
  479.                     h = self.html.headerHeader.clone()
  480.                     for header in options[('html_ui', 'display_headers')]:
  481.                         h.headerLink.href = 'review?sort=%sHeader' % (header.lower(),)
  482.                         h.headerName = header.title()
  483.                         sh.optionalHeaders += h
  484.                     
  485.                     if not options[('html_ui', 'display_score')]:
  486.                         del sh.score_header
  487.                     
  488.                     if not options[('html_ui', 'display_received_time')]:
  489.                         del sh.received_header
  490.                     
  491.                     subHeader = str(sh)
  492.                     subHeader = subHeader.replace('TYPE', label)
  493.                     page.table += self.html.blankRow
  494.                     page.table += subHeader
  495.                     self._appendMessages(page.table, messages, label, sort_order, reverse)
  496.                     continue
  497.                 page
  498.             
  499.             page.table += self.html.trainRow
  500.             box = self._buildBox(title, None, page)
  501.         else:
  502.             page = _("<p>There are no untrained messages to display. Return <a href='home'>Home</a>, or <a href='review'>check again</a>.</p>")
  503.             title = _('No untrained messages')
  504.             box = self._buildBox(title, 'status.gif', page)
  505.         self.write(box)
  506.         self._writePostamble(help_topic = 'review')
  507.  
  508.     
  509.     def _contains(self, a, b, ignore_case = False):
  510.         '''Return true if substring b is part of string a.'''
  511.         if not isinstance(a, types.StringTypes):
  512.             raise AssertionError
  513.         if not isinstance(b, types.StringTypes):
  514.             raise AssertionError
  515.         if ignore_case:
  516.             a = a.lower()
  517.             b = b.lower()
  518.         
  519.         return a.find(b) >= 0
  520.  
  521.     
  522.     def onView(self, key, corpus):
  523.         '''View a message - linked from the Review page.'''
  524.         self._writePreamble(_('View message'), parent = ('review', _('Review')))
  525.         sourceCorpus = None
  526.         message = None
  527.         if state.unknownCorpus.get(key) is not None:
  528.             sourceCorpus = state.unknownCorpus
  529.         elif state.hamCorpus.get(key) is not None:
  530.             sourceCorpus = state.hamCorpus
  531.         elif state.spamCorpus.get(key) is not None:
  532.             sourceCorpus = state.spamCorpus
  533.         
  534.         if sourceCorpus is not None:
  535.             message = sourceCorpus.get(key)
  536.         
  537.         if message is not None:
  538.             self.write('<pre>%s</pre>' % cgi.escape(message.as_string()))
  539.         else:
  540.             self.write(_("<p>Can't find message %r. Maybe it expired.</p>") % key)
  541.         self._writePostamble()
  542.  
  543.     
  544.     def onShowclues(self, key, subject, tokens = '0'):
  545.         '''Show clues for a message - linked from the Review page.'''
  546.         tokens = bool(int(tokens))
  547.         self._writePreamble(_('Message clues'), parent = ('review', _('Review')))
  548.         sourceCorpus = None
  549.         message = None
  550.         if state.unknownCorpus.get(key) is not None:
  551.             sourceCorpus = state.unknownCorpus
  552.         elif state.hamCorpus.get(key) is not None:
  553.             sourceCorpus = state.hamCorpus
  554.         elif state.spamCorpus.get(key) is not None:
  555.             sourceCorpus = state.spamCorpus
  556.         
  557.         if sourceCorpus is not None:
  558.             message = sourceCorpus.get(key).as_string()
  559.         
  560.         if message is not None:
  561.             message = message.replace('\r\n', '\n').replace('\r', '\n')
  562.             results = self._buildCluesTable(message, subject, tokens)
  563.             del results.classifyAnother
  564.             self.write(results)
  565.         else:
  566.             self.write(_("<p>Can't find message %r. Maybe it expired.</p>") % key)
  567.         self._writePostamble()
  568.  
  569.     
  570.     def _makeMessageInfo(self, message):
  571.         '''Given an email.Message, return an object with subjectHeader,
  572.         bodySummary and other header (as needed) attributes.  These objects
  573.         are passed into appendMessages by onReview - passing email.Message
  574.         objects directly uses too much memory.
  575.         '''
  576.         message.delNotations()
  577.         if not message['Subject']:
  578.             pass
  579.         subjectHeader = '(none)'
  580.         headers = {
  581.             'subject': subjectHeader }
  582.         for header in options[('html_ui', 'display_headers')]:
  583.             if not message[header]:
  584.                 pass
  585.             headers[header.lower()] = '(none)'
  586.         
  587.         score = message[options[('Headers', 'score_header_name')]]
  588.         if score:
  589.             op = score.find('(')
  590.             if op >= 0:
  591.                 score = score[:op]
  592.             
  593.             
  594.             try:
  595.                 score = float(score) * 100
  596.             except ValueError:
  597.                 score = 'Err'
  598.             except:
  599.                 None<EXCEPTION MATCH>ValueError
  600.             
  601.  
  602.         None<EXCEPTION MATCH>ValueError
  603.         score = '?'
  604.         
  605.         try:
  606.             part = typed_subpart_iterator(message, 'text', 'plain').next()
  607.             text = part.get_payload()
  608.         except StopIteration:
  609.             
  610.             try:
  611.                 part = typed_subpart_iterator(message, 'text', 'html').next()
  612.                 text = part.get_payload()
  613.                 (text, unused) = tokenizer.crack_html_style(text)
  614.                 (text, unused) = tokenizer.crack_html_comment(text)
  615.                 text = tokenizer.html_re.sub(' ', text)
  616.                 text = _('(this message only has an HTML body)\n') + text
  617.             except StopIteration:
  618.                 text = _('(this message has no text body)')
  619.             except:
  620.                 None<EXCEPTION MATCH>StopIteration
  621.             
  622.  
  623.             None<EXCEPTION MATCH>StopIteration
  624.  
  625.         if type(text) == type([]):
  626.             text = _('(this message is a digest of %s messages)') % len(text)
  627.         elif text is None:
  628.             text = _('(this message has no body)')
  629.         else:
  630.             text = text.replace(' ', ' ')
  631.             text = re.sub('(\\s)\\s+', '\\1', text)
  632.             text = text.strip()
  633.         
  634.         class _MessageInfo:
  635.             pass
  636.  
  637.         messageInfo = _MessageInfo()
  638.         for headerName, headerValue in headers.items():
  639.             headerValue = self._trimHeader(headerValue, 45, True)
  640.             setattr(messageInfo, '%sHeader' % (headerName,), headerValue)
  641.         
  642.         messageInfo.score = score
  643.         messageInfo.bodySummary = self._trimHeader(text, 200)
  644.         return messageInfo
  645.  
  646.     
  647.     def close_database(self):
  648.         state.close()
  649.  
  650.     
  651.     def reReadOptions(self):
  652.         '''Called by the config page when the user saves some new options, or
  653.         restores the defaults.'''
  654.         global options, state
  655.         import Options
  656.         Options.load_options()
  657.         options = options
  658.         import Options
  659.         state = self.state_recreator()
  660.         self.classifier = state.bayes
  661.  
  662.     
  663.     def verifyInput(self, parms, pmap):
  664.         '''Check that the given input is valid.'''
  665.         errmsg = UserInterface.UserInterface.verifyInput(self, parms, pmap)
  666.         if pmap != parm_ini_map:
  667.             return errmsg
  668.         
  669.         slist = list(parms['pop3proxy_remote_servers'])
  670.         plist = list(parms['pop3proxy_listen_ports'])
  671.         if len(slist) != len(plist):
  672.             errmsg += _('<li>The number of POP3 proxy ports specified must match the number of servers specified</li>\n')
  673.         
  674.         plist.sort()
  675.         for p in range(len(plist) - 1):
  676.             
  677.             try:
  678.                 if plist[p] == plist[p + 1]:
  679.                     errmsg += _('<li>All POP3 port numbers must be unique</li>')
  680.                     break
  681.             continue
  682.             except IndexError:
  683.                 continue
  684.             
  685.  
  686.         
  687.         slist = list(parms['smtpproxy_remote_servers'])
  688.         plist = list(parms['smtpproxy_listen_ports'])
  689.         if len(slist) != len(plist):
  690.             errmsg += _('<li>The number of SMTP proxy ports specified must match the number of servers specified</li>\n')
  691.         
  692.         plist.sort()
  693.         for p in range(len(plist) - 1):
  694.             
  695.             try:
  696.                 if plist[p] == plist[p + 1]:
  697.                     errmsg += _('<li>All SMTP port numbers must be unique</li>')
  698.                     break
  699.             continue
  700.             except IndexError:
  701.                 continue
  702.             
  703.  
  704.         
  705.         return errmsg
  706.  
  707.  
  708.